//	---------------------------------------------------------------------------
//
//	@file		TwOpenGLCore.cpp
//	@author		Philippe Decaudin
//	@license	This file is part of the AntTweakBar library.
//				For conditions of distribution and use, see License.txt
//
//	---------------------------------------------------------------------------

#if !defined ANT_TW_NO_CORE_GL

/*
#pragma warning GL3				//// used for development
#define GL3_PROTOTYPES 1		////
#include <GL3/gl3.h>			////
#define ANT_OGL_HEADER_INCLUDED ////
*/

#if defined __APPLE__
#	include <OpenGL/gl3.h>
#	define ANT_OGL_HEADER_INCLUDED
#endif
#include "TwPrecomp.h"
#include "LoadOGLCore.h"
#include "TwOpenGLCore.h"

namespace AntTweakBar {

using namespace std;

static const char *g_ErrCantLoadOGL    = "Cannot load OpenGL library dynamically";
static const char *g_ErrCantUnloadOGL  = "Cannot unload OpenGL library";

//	---------------------------------------------------------------------------

#ifdef _DEBUG
	static void CheckGLCoreError(const char *file, int line, const char *func)
	{
		int err=0;
		char msg[256];
		while( (err=_glGetError())!=0 )
		{
			sprintf(msg, "%s(%d) : [%s] GL_CORE_ERROR=0x%x\n", file, line, func, err);
			#ifdef ANT_WINDOWS
				OutputDebugString(msg);
			#endif
			fprintf(stderr, "%s", msg);
		}
	}
#	ifdef __FUNCTION__
#		define CHECK_GL_ERROR CheckGLCoreError(__FILE__, __LINE__, __FUNCTION__)
#	else
#		define CHECK_GL_ERROR CheckGLCoreError(__FILE__, __LINE__, "")
#	endif
#else
#	define CHECK_GL_ERROR ((void)(0))
#endif

//	---------------------------------------------------------------------------

static GLuint BindFont(const CTexFont *_Font, bool useGlES)
{
	GLuint TexID = 0;
	_glGenTextures(1, &TexID);
	_glBindTexture(GL_TEXTURE_2D, TexID);
	CHECK_GL_ERROR;
	if ( !useGlES )
	{
		_glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
		_glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
	}
	_glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	_glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	_glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	CHECK_GL_ERROR;
	_glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	_glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, _Font->m_TexWidth, _Font->m_TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, _Font->m_TexBytes);
	_glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	_glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	_glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	_glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	_glBindTexture(GL_TEXTURE_2D, 0);
	CHECK_GL_ERROR;

	return TexID;
}

static void UnbindFont(GLuint _FontTexID)
{
	if( _FontTexID>0 )
		_glDeleteTextures(1, &_FontTexID);
}

//	---------------------------------------------------------------------------

static GLuint CreateShader(GLchar const* source, GLenum type, bool useGlES)
{
	GLuint shader = _glCreateShader(type);

	char const* sourceStrings[5];
	sourceStrings[0] = useGlES ? "#version 300 es\n" : "#version 140\n";
	sourceStrings[1] =
			"#if __VERSION__ >= 130\n"

				"#define IN in\n"
				"#define OUT out\n"
				"#define FRAGHDR out vec4 outColor;\n"
				"#define FRAGOUT outColor\n"
				"#define TEXTURE texture\n"

			"#else\n"

			;
	sourceStrings[2] =
			type == GL_VERTEX_SHADER ?
				"#define IN attribute\n"
				"#define OUT varying\n"
			:
				"#define IN varying\n"
				"#define FRAGHDR\n"
				"#define FRAGOUT gl_FragColor\n"
				"#define TEXTURE texture2D\n"
			;
	sourceStrings[3] =
			"#endif\n"
			;
	sourceStrings[4] = source;

	_glShaderSource(shader, 5, sourceStrings, NULL);
	_glCompileShader(shader); CHECK_GL_ERROR;

	GLint status;
	_glGetShaderiv(shader, GL_COMPILE_STATUS, &status); CHECK_GL_ERROR;
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		_glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); CHECK_GL_ERROR;

		GLchar strInfoLog[256];
		_glGetShaderInfoLog(shader, sizeof(strInfoLog), NULL, strInfoLog); CHECK_GL_ERROR;
#ifdef ANT_WINDOWS
		OutputDebugString("Compile failure: ");
		OutputDebugString(strInfoLog);
		OutputDebugString("\n");
#endif
		fprintf(stderr, "Compile failure: %s\n", strInfoLog);
		shader = 0;
	}

	return shader;
}

static GLuint LinkProgram(GLuint program)
{
	_glLinkProgram(program); CHECK_GL_ERROR;

	GLint status;
	_glGetProgramiv(program, GL_LINK_STATUS, &status); CHECK_GL_ERROR;
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		_glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); CHECK_GL_ERROR;

		GLchar strInfoLog[256];
		_glGetProgramInfoLog(program, sizeof(strInfoLog), NULL, strInfoLog); CHECK_GL_ERROR;
#ifdef ANT_WINDOWS
		OutputDebugString("Linker failure: ");
		OutputDebugString(strInfoLog);
		OutputDebugString("\n");
#endif
		fprintf(stderr, "Linker failure: %s\n", strInfoLog);
		program = 0;
	}

	return program;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::ResizeTriBuffers(size_t _NewSize)
{
	m_TriBufferSize = _NewSize;

	_glBindVertexArray(m_TriVArray);

	_glBindBuffer(GL_ARRAY_BUFFER, m_TriVertices);
	_glBufferData(GL_ARRAY_BUFFER, m_TriBufferSize*sizeof(Vec2), 0, GL_DYNAMIC_DRAW);

	_glBindBuffer(GL_ARRAY_BUFFER, m_TriUVs);
	_glBufferData(GL_ARRAY_BUFFER, m_TriBufferSize*sizeof(Vec2), 0, GL_DYNAMIC_DRAW);

	_glBindBuffer(GL_ARRAY_BUFFER, m_TriColors);
	_glBufferData(GL_ARRAY_BUFFER, m_TriBufferSize*sizeof(color32), 0, GL_DYNAMIC_DRAW);

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

int CTwGraphOpenGLCore::Init()
{
	m_Drawing = false;
	m_FontTexID = 0;
	m_FontTex = NULL;

	if( LoadOpenGLCore()==0 )
	{
		g_ErrorState = g_ErrCantLoadOGL;
		return 0;
	}

	char const* glVersion = (char const*) _glGetString( GL_VERSION );
	m_UseOpenGLES = strncmp( glVersion, "OpenGL ES", 9 ) == 0;

	// Create line/rect shaders
	const GLchar *lineRectVS =
			"precision highp float;"
			"IN vec3 vertex;"
			"IN vec4 color;"
			"OUT vec4 fcolor;"
			"void main() { gl_Position = vec4(vertex, 1); fcolor = color.bgra; }";

	m_LineRectVS = CreateShader(lineRectVS, GL_VERTEX_SHADER, m_UseOpenGLES);

	const GLchar *lineRectFS =
			"precision mediump float;"
			"FRAGHDR "
			"IN vec4 fcolor;"
			"void main() { FRAGOUT = fcolor; }";

	m_LineRectFS = CreateShader(lineRectFS, GL_FRAGMENT_SHADER, m_UseOpenGLES);

	m_LineRectProgram = _glCreateProgram();
	_glAttachShader(m_LineRectProgram, m_LineRectVS);
	_glAttachShader(m_LineRectProgram, m_LineRectFS);
	_glBindAttribLocation(m_LineRectProgram, 0, "vertex");
	_glBindAttribLocation(m_LineRectProgram, 1, "color");
	LinkProgram(m_LineRectProgram);

	// Create line/rect vertex buffer
	const GLfloat lineRectInitVertices[] = { 0,0,0, 0,0,0, 0,0,0, 0,0,0 };
	const color32 lineRectInitColors[] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
	_glGenVertexArrays(1, &m_LineRectVArray);
	_glBindVertexArray(m_LineRectVArray);
	_glGenBuffers(1, &m_LineRectVertices);
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectVertices);
	_glBufferData(GL_ARRAY_BUFFER, sizeof(lineRectInitVertices), lineRectInitVertices, GL_DYNAMIC_DRAW);
	_glGenBuffers(1, &m_LineRectColors);
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectColors);
	_glBufferData(GL_ARRAY_BUFFER, sizeof(lineRectInitColors), lineRectInitColors, GL_DYNAMIC_DRAW);

	// Create triangles shaders
	const GLchar *triVS =
			"precision highp float;"
			"uniform vec2 offset;"
			"uniform vec2 wndSize;"
			"IN vec2 vertex;"
			"IN vec4 color;"
			"OUT vec4 fcolor;"
			"void main() {"
			" gl_Position = vec4(2.0*(vertex.x+offset.x-0.5)/wndSize.x - 1.0, 1.0 - 2.0*(vertex.y+offset.y-0.5)/wndSize.y, 0, 1);"
			" fcolor = color.bgra;"
			"}";

	m_TriVS = CreateShader(triVS, GL_VERTEX_SHADER, m_UseOpenGLES);

	const GLchar *triUniVS =
			"precision highp float;"
			"uniform vec2 offset;"
			"uniform vec2 wndSize;"
			"uniform vec4 color;"
			"IN vec2 vertex;"
			"OUT vec4 fcolor;"
			"void main() {"
			" gl_Position = vec4(2.0*(vertex.x+offset.x-0.5)/wndSize.x - 1.0, 1.0 - 2.0*(vertex.y+offset.y-0.5)/wndSize.y, 0, 1);"
			" fcolor = color.bgra;"
			"}";

	m_TriUniVS = CreateShader(triUniVS, GL_VERTEX_SHADER, m_UseOpenGLES);

	m_TriFS = m_TriUniFS = m_LineRectFS;

	m_TriProgram = _glCreateProgram();
	_glAttachShader(m_TriProgram, m_TriVS);
	_glAttachShader(m_TriProgram, m_TriFS);
	_glBindAttribLocation(m_TriProgram, 0, "vertex");
	_glBindAttribLocation(m_TriProgram, 1, "color");
	LinkProgram(m_TriProgram);
	m_TriLocationOffset = _glGetUniformLocation(m_TriProgram, "offset");
	m_TriLocationWndSize = _glGetUniformLocation(m_TriProgram, "wndSize");

	m_TriUniProgram = _glCreateProgram();
	_glAttachShader(m_TriUniProgram, m_TriUniVS);
	_glAttachShader(m_TriUniProgram, m_TriUniFS);
	_glBindAttribLocation(m_TriUniProgram, 0, "vertex");
	_glBindAttribLocation(m_TriUniProgram, 1, "color");
	LinkProgram(m_TriUniProgram);
	m_TriUniLocationOffset = _glGetUniformLocation(m_TriUniProgram, "offset");
	m_TriUniLocationWndSize = _glGetUniformLocation(m_TriUniProgram, "wndSize");
	m_TriUniLocationColor = _glGetUniformLocation(m_TriUniProgram, "color");

	const GLchar *triTexFS =
			"precision mediump float;"
			"FRAGHDR "
			"uniform sampler2D tex;"
			"IN vec2 fuv;"
			"IN vec4 fcolor;"
			"void main() { FRAGOUT.rgb = fcolor.bgr; FRAGOUT.a = fcolor.a * TEXTURE(tex, fuv).r; }";

	m_TriTexFS = CreateShader(triTexFS, GL_FRAGMENT_SHADER, m_UseOpenGLES);

	const GLchar *triTexVS =
			"precision highp float;"
			"uniform vec2 offset;"
			"uniform vec2 wndSize;"
			"IN vec2 vertex;"
			"IN vec2 uv;"
			"IN vec4 color;"
			"OUT vec2 fuv;"
			"OUT vec4 fcolor;"
			"void main() {"
			" gl_Position = vec4(2.0*(vertex.x+offset.x-0.5)/wndSize.x - 1.0, 1.0 - 2.0*(vertex.y+offset.y-0.5)/wndSize.y, 0, 1);"
			" fuv = uv;"
			" fcolor = color.bgra;"
			"}";

	m_TriTexVS = CreateShader(triTexVS, GL_VERTEX_SHADER, m_UseOpenGLES);

	const GLchar *triTexUniVS =
			"precision highp float;"
			"uniform vec2 offset;"
			"uniform vec2 wndSize;"
			"uniform vec4 color;"
			"IN vec2 vertex;"
			"IN vec2 uv;"
			"OUT vec4 fcolor;"
			"OUT vec2 fuv;"
			"void main() {"
			" gl_Position = vec4(2.0*(vertex.x+offset.x-0.5)/wndSize.x - 1.0, 1.0 - 2.0*(vertex.y+offset.y-0.5)/wndSize.y, 0, 1);"
			" fuv = uv;"
			" fcolor = color.bgra;"
			"}";

	m_TriTexUniVS = CreateShader(triTexUniVS, GL_VERTEX_SHADER, m_UseOpenGLES);

	m_TriTexUniFS = m_TriTexFS;

	m_TriTexProgram = _glCreateProgram();
	_glAttachShader(m_TriTexProgram, m_TriTexVS);
	_glAttachShader(m_TriTexProgram, m_TriTexFS);
	_glBindAttribLocation(m_TriTexProgram, 0, "vertex");
	_glBindAttribLocation(m_TriTexProgram, 1, "uv");
	_glBindAttribLocation(m_TriTexProgram, 2, "color");
	LinkProgram(m_TriTexProgram);
	m_TriTexLocationOffset = _glGetUniformLocation(m_TriTexProgram, "offset");
	m_TriTexLocationWndSize = _glGetUniformLocation(m_TriTexProgram, "wndSize");
	m_TriTexLocationTexture = _glGetUniformLocation(m_TriTexProgram, "tex");

	m_TriTexUniProgram = _glCreateProgram();
	_glAttachShader(m_TriTexUniProgram, m_TriTexUniVS);
	_glAttachShader(m_TriTexUniProgram, m_TriTexUniFS);
	_glBindAttribLocation(m_TriTexUniProgram, 0, "vertex");
	_glBindAttribLocation(m_TriTexUniProgram, 1, "uv");
	_glBindAttribLocation(m_TriTexUniProgram, 2, "color");
	LinkProgram(m_TriTexUniProgram);
	m_TriTexUniLocationOffset = _glGetUniformLocation(m_TriTexUniProgram, "offset");
	m_TriTexUniLocationWndSize = _glGetUniformLocation(m_TriTexUniProgram, "wndSize");
	m_TriTexUniLocationColor = _glGetUniformLocation(m_TriTexUniProgram, "color");
	m_TriTexUniLocationTexture = _glGetUniformLocation(m_TriTexUniProgram, "tex");

	// Create tri vertex buffer
	_glGenVertexArrays(1, &m_TriVArray);
	_glGenBuffers(1, &m_TriVertices);
	_glGenBuffers(1, &m_TriUVs);
	_glGenBuffers(1, &m_TriColors);
	ResizeTriBuffers(16384); // set initial size

	CHECK_GL_ERROR;
	return 1;
}

//	---------------------------------------------------------------------------

int CTwGraphOpenGLCore::Shut()
{
	assert(m_Drawing==false);

	UnbindFont(m_FontTexID);

	CHECK_GL_ERROR;

	_glDeleteProgram(m_LineRectProgram); m_LineRectProgram = 0;
	_glDeleteShader(m_LineRectVS); m_LineRectVS = 0;
	_glDeleteShader(m_LineRectFS); m_LineRectFS = 0;

	_glDeleteProgram(m_TriProgram); m_TriProgram = 0;
	_glDeleteShader(m_TriVS); m_TriVS = 0;

	_glDeleteProgram(m_TriUniProgram); m_TriUniProgram = 0;
	_glDeleteShader(m_TriUniVS); m_TriUniVS = 0;

	_glDeleteProgram(m_TriTexProgram); m_TriTexProgram = 0;
	_glDeleteShader(m_TriTexVS); m_TriTexVS = 0;
	_glDeleteShader(m_TriTexFS); m_TriTexFS = 0;

	_glDeleteProgram(m_TriTexUniProgram); m_TriTexUniProgram = 0;
	_glDeleteShader(m_TriTexUniVS); m_TriTexUniVS = 0;

	_glDeleteBuffers(1, &m_LineRectVertices); m_LineRectVertices = 0;
	_glDeleteBuffers(1, &m_LineRectColors); m_LineRectColors = 0;
	_glDeleteVertexArrays(1, &m_LineRectVArray); m_LineRectVArray = 0;

	_glDeleteBuffers(1, &m_TriVertices); m_TriVertices = 0;
	_glDeleteBuffers(1, &m_TriColors); m_TriColors = 0;
	_glDeleteBuffers(1, &m_TriUVs); m_TriUVs = 0;
	_glDeleteVertexArrays(1, &m_TriVArray); m_TriVArray = 0;

	CHECK_GL_ERROR;

	int Res = 1;
	if( UnloadOpenGLCore()==0 )
	{
		g_ErrorState = g_ErrCantUnloadOGL;
		Res = 0;
	}

	return Res;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::BeginDraw(int _WndWidth, int _WndHeight)
{
	CHECK_GL_ERROR;
	assert(m_Drawing==false && _WndWidth>0 && _WndHeight>0);
	m_Drawing = true;
	m_WndWidth = _WndWidth;
	m_WndHeight = _WndHeight;
	m_OffsetX = 0;
	m_OffsetY = 0;

	_glGetIntegerv(GL_VIEWPORT, m_PrevViewport); CHECK_GL_ERROR;
	if( _WndWidth>0 && _WndHeight>0 )
	{
		GLint Vp[4];
		Vp[0] = 0;
		Vp[1] = 0;
		Vp[2] = _WndWidth-1;
		Vp[3] = _WndHeight-1;
		_glViewport(Vp[0], Vp[1], Vp[2], Vp[3]);
	}

	m_PrevVArray = 0;
	_glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&m_PrevVArray); CHECK_GL_ERROR;
	_glBindVertexArray(0); CHECK_GL_ERROR;

	m_PrevLineWidth = 1;
	_glGetFloatv(GL_LINE_WIDTH, &m_PrevLineWidth); CHECK_GL_ERROR;
	_glLineWidth(1); CHECK_GL_ERROR;

	if (!m_UseOpenGLES)
	{
		m_PrevLineSmooth = _glIsEnabled(GL_LINE_SMOOTH);
		_glDisable(GL_LINE_SMOOTH); CHECK_GL_ERROR;
	}

	m_PrevCullFace = _glIsEnabled(GL_CULL_FACE);
	_glDisable(GL_CULL_FACE); CHECK_GL_ERROR;

	m_PrevDepthTest = _glIsEnabled(GL_DEPTH_TEST);
	_glDisable(GL_DEPTH_TEST); CHECK_GL_ERROR;

	m_PrevBlend = _glIsEnabled(GL_BLEND);
	_glEnable(GL_BLEND); CHECK_GL_ERROR;

	m_PrevScissorTest = _glIsEnabled(GL_SCISSOR_TEST);
	_glDisable(GL_SCISSOR_TEST); CHECK_GL_ERROR;

	_glGetIntegerv(GL_SCISSOR_BOX, m_PrevScissorBox); CHECK_GL_ERROR;

	_glGetIntegerv(GL_BLEND_SRC_RGB, &m_PrevSrcBlendCol); CHECK_GL_ERROR;
	_glGetIntegerv(GL_BLEND_DST_RGB, &m_PrevDstBlendCol); CHECK_GL_ERROR;
	_glGetIntegerv(GL_BLEND_SRC_ALPHA, &m_PrevSrcBlendA); CHECK_GL_ERROR;
	_glGetIntegerv(GL_BLEND_DST_ALPHA, &m_PrevDstBlendA); CHECK_GL_ERROR;
	_glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CHECK_GL_ERROR;

	m_PrevProgramObject = 0;
	_glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&m_PrevProgramObject); CHECK_GL_ERROR;
	_glBindVertexArray(0); CHECK_GL_ERROR;
	_glUseProgram(0); CHECK_GL_ERROR;

	m_PrevActiveTexture = 0;
	_glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&m_PrevActiveTexture); CHECK_GL_ERROR;

	_glActiveTexture(GL_TEXTURE0); // this is the one we'll be using

	m_PrevTexture = 0;
	_glGetIntegerv(GL_TEXTURE_BINDING_2D, &m_PrevTexture); CHECK_GL_ERROR;
	_glBindTexture(GL_TEXTURE_2D, 0); CHECK_GL_ERROR;

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::EndDraw()
{
	assert(m_Drawing==true);
	m_Drawing = false;

	_glLineWidth(m_PrevLineWidth); CHECK_GL_ERROR;

	if( !m_UseOpenGLES )
	{
		if( m_PrevLineSmooth )
			_glEnable(GL_LINE_SMOOTH);
		else
			_glDisable(GL_LINE_SMOOTH);
		CHECK_GL_ERROR;
	}

	if( m_PrevCullFace )
		_glEnable(GL_CULL_FACE);
	else
		_glDisable(GL_CULL_FACE);
	CHECK_GL_ERROR;

	if( m_PrevDepthTest )
		_glEnable(GL_DEPTH_TEST);
	else
		_glDisable(GL_DEPTH_TEST);
	CHECK_GL_ERROR;

	if( m_PrevBlend )
		_glEnable(GL_BLEND);
	else
		_glDisable(GL_BLEND);
	CHECK_GL_ERROR;

	if( m_PrevScissorTest )
		_glEnable(GL_SCISSOR_TEST);
	else
		_glDisable(GL_SCISSOR_TEST);
	CHECK_GL_ERROR;

	_glScissor(m_PrevScissorBox[0], m_PrevScissorBox[1], m_PrevScissorBox[2], m_PrevScissorBox[3]); CHECK_GL_ERROR;

	_glBlendFuncSeparate(m_PrevSrcBlendCol, m_PrevDstBlendCol, m_PrevSrcBlendA, m_PrevDstBlendA ); CHECK_GL_ERROR;

	_glBindTexture(GL_TEXTURE_2D, m_PrevTexture); CHECK_GL_ERROR;

	_glActiveTexture(m_PrevActiveTexture); // must come after texture restore

	_glUseProgram(m_PrevProgramObject); CHECK_GL_ERROR;

	_glBindVertexArray(m_PrevVArray); CHECK_GL_ERROR;

	_glViewport(m_PrevViewport[0], m_PrevViewport[1], m_PrevViewport[2], m_PrevViewport[3]); CHECK_GL_ERROR;

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

bool CTwGraphOpenGLCore::IsDrawing()
{
	return m_Drawing;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::Restore()
{
	UnbindFont(m_FontTexID);
	m_FontTexID = 0;
	m_FontTex = NULL;
}

//	---------------------------------------------------------------------------

static inline float ToNormScreenX(float x, int wndWidth)
{
	return 2.0f*((float)x-0.5f)/wndWidth - 1.0f;
}

static inline float ToNormScreenY(float y, int wndHeight)
{
	return 1.0f - 2.0f*((float)y-0.5f)/wndHeight;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::DrawLine(int _X0, int _Y0, int _X1, int _Y1, color32 _Color0, color32 _Color1, bool _AntiAliased)
{
	CHECK_GL_ERROR;
	assert(m_Drawing==true);

	//const GLfloat dx = +0.0f;
	const GLfloat dx = 0;
	//GLfloat dy = -0.2f;
	const GLfloat dy = -0.5f;

	if ( !m_UseOpenGLES )
	{
		if( _AntiAliased )
			_glEnable(GL_LINE_SMOOTH);
		else
			_glDisable(GL_LINE_SMOOTH);
	}

	_glBindVertexArray(m_LineRectVArray);

	GLfloat x0 = ToNormScreenX(_X0+dx + m_OffsetX, m_WndWidth);
	GLfloat y0 = ToNormScreenY(_Y0+dy + m_OffsetY, m_WndHeight);
	GLfloat x1 = ToNormScreenX(_X1+dx + m_OffsetX, m_WndWidth);
	GLfloat y1 = ToNormScreenY(_Y1+dy + m_OffsetY, m_WndHeight);
	GLfloat vertices[] = { x0,y0,0,  x1,y1,0 };
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectVertices);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
	_glVertexAttribPointer(0, 3, GL_FLOAT, GL_TRUE, 0, NULL);
	_glEnableVertexAttribArray(0);

	color32 colors[] = { _Color0, _Color1 };
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectColors);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(colors), colors);
	_glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);
	_glEnableVertexAttribArray(1);

	_glUseProgram(m_LineRectProgram);
	_glDrawArrays(GL_LINES, 0, 2);

	if( !m_UseOpenGLES && _AntiAliased )
		_glDisable(GL_LINE_SMOOTH);

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::DrawRect(int _X0, int _Y0, int _X1, int _Y1, color32 _Color00, color32 _Color10, color32 _Color01, color32 _Color11)
{
	CHECK_GL_ERROR;
	assert(m_Drawing==true);

	// border adjustment
	if(_X0<_X1)
		++_X1;
	else if(_X0>_X1)
		++_X0;
	if(_Y0<_Y1)
		--_Y0;
	else if(_Y0>_Y1)
		--_Y1;

	_glBindVertexArray(m_LineRectVArray);

	GLfloat x0 = ToNormScreenX((float)_X0 + m_OffsetX, m_WndWidth);
	GLfloat y0 = ToNormScreenY((float)_Y0 + m_OffsetY, m_WndHeight);
	GLfloat x1 = ToNormScreenX((float)_X1 + m_OffsetX, m_WndWidth);
	GLfloat y1 = ToNormScreenY((float)_Y1 + m_OffsetY, m_WndHeight);
	GLfloat vertices[] = { x0,y0,0, x1,y0,0, x0,y1,0, x1,y1,0 };
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectVertices);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
	_glVertexAttribPointer(0, 3, GL_FLOAT, GL_TRUE, 0, NULL);
	_glEnableVertexAttribArray(0);

	GLuint colors[] = { _Color00, _Color10, _Color01, _Color11 };
	_glBindBuffer(GL_ARRAY_BUFFER, m_LineRectColors);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(colors), colors);
	_glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);
	_glEnableVertexAttribArray(1);

	_glUseProgram(m_LineRectProgram);
	_glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

void *CTwGraphOpenGLCore::NewTextObj()
{
	return new CTextObj;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::DeleteTextObj(void *_TextObj)
{
	assert(_TextObj!=NULL);
	delete static_cast<CTextObj *>(_TextObj);
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::BuildText(void *_TextObj, const std::string *_TextLines, color32 *_LineColors, color32 *_LineBgColors, int _NbLines, const CTexFont *_Font, int _Sep, int _BgWidth)
{
	assert(m_Drawing==true);
	assert(_TextObj!=NULL);
	assert(_Font!=NULL);

	if( _Font != m_FontTex )
	{
		UnbindFont(m_FontTexID);
		m_FontTexID = BindFont(_Font, m_UseOpenGLES);
		m_FontTex = _Font;
	}
	CTextObj *TextObj = static_cast<CTextObj *>(_TextObj);
	TextObj->m_TextVerts.resize(0);
	TextObj->m_TextUVs.resize(0);
	TextObj->m_BgVerts.resize(0);
	TextObj->m_Colors.resize(0);
	TextObj->m_BgColors.resize(0);

	int x, x1, y, y1, i, Len;
	unsigned char ch;
	const unsigned char *Text;
	color32 LineColor = COLOR32_RED;
	for( int Line=0; Line<_NbLines; ++Line )
	{
		x = 0;
		y = Line * (_Font->m_CharHeight+_Sep);
		y1 = y+_Font->m_CharHeight;
		Len = (int)_TextLines[Line].length();
		Text = (const unsigned char *)(_TextLines[Line].c_str());
		if( _LineColors!=NULL )
			LineColor = (_LineColors[Line]&0xff00ff00) | GLubyte(_LineColors[Line]>>16) | (GLubyte(_LineColors[Line])<<16);

		for( i=0; i<Len; ++i )
		{
			ch = Text[i];
			x1 = x + _Font->m_CharWidth[ch];

			TextObj->m_TextVerts.push_back(Vec2(x , y ));
			TextObj->m_TextVerts.push_back(Vec2(x1, y ));
			TextObj->m_TextVerts.push_back(Vec2(x , y1));
			TextObj->m_TextVerts.push_back(Vec2(x1, y ));
			TextObj->m_TextVerts.push_back(Vec2(x1, y1));
			TextObj->m_TextVerts.push_back(Vec2(x , y1));

			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU0[ch], _Font->m_CharV0[ch]));
			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU1[ch], _Font->m_CharV0[ch]));
			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU0[ch], _Font->m_CharV1[ch]));
			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU1[ch], _Font->m_CharV0[ch]));
			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU1[ch], _Font->m_CharV1[ch]));
			TextObj->m_TextUVs.push_back(Vec2(_Font->m_CharU0[ch], _Font->m_CharV1[ch]));

			if( _LineColors!=NULL )
			{
				TextObj->m_Colors.push_back(LineColor);
				TextObj->m_Colors.push_back(LineColor);
				TextObj->m_Colors.push_back(LineColor);
				TextObj->m_Colors.push_back(LineColor);
				TextObj->m_Colors.push_back(LineColor);
				TextObj->m_Colors.push_back(LineColor);
			}

			x = x1;
		}
		if( _BgWidth>0 )
		{
			TextObj->m_BgVerts.push_back(Vec2(-1		, y ));
			TextObj->m_BgVerts.push_back(Vec2(_BgWidth+1, y ));
			TextObj->m_BgVerts.push_back(Vec2(-1		, y1));
			TextObj->m_BgVerts.push_back(Vec2(_BgWidth+1, y ));
			TextObj->m_BgVerts.push_back(Vec2(_BgWidth+1, y1));
			TextObj->m_BgVerts.push_back(Vec2(-1		, y1));

			if( _LineBgColors!=NULL )
			{
				color32 LineBgColor = (_LineBgColors[Line]&0xff00ff00) | GLubyte(_LineBgColors[Line]>>16) | (GLubyte(_LineBgColors[Line])<<16);
				TextObj->m_BgColors.push_back(LineBgColor);
				TextObj->m_BgColors.push_back(LineBgColor);
				TextObj->m_BgColors.push_back(LineBgColor);
				TextObj->m_BgColors.push_back(LineBgColor);
				TextObj->m_BgColors.push_back(LineBgColor);
				TextObj->m_BgColors.push_back(LineBgColor);
			}
		}
	}
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::DrawText(void *_TextObj, int _X, int _Y, color32 _Color, color32 _BgColor)
{
	CHECK_GL_ERROR;
	assert(m_Drawing==true);
	assert(_TextObj!=NULL);
	CTextObj *TextObj = static_cast<CTextObj *>(_TextObj);

	if( TextObj->m_TextVerts.size()<4 && TextObj->m_BgVerts.size()<4 )
		return; // nothing to draw

	// draw character background triangles
	if( (_BgColor!=0 || TextObj->m_BgColors.size()==TextObj->m_BgVerts.size()) && TextObj->m_BgVerts.size()>=4 )
	{
		size_t numBgVerts = TextObj->m_BgVerts.size();
		if( numBgVerts > m_TriBufferSize )
			ResizeTriBuffers(numBgVerts + 2048);

		_glBindVertexArray(m_TriVArray);

		_glBindBuffer(GL_ARRAY_BUFFER, m_TriVertices);
		_glBufferSubData(GL_ARRAY_BUFFER, 0, numBgVerts*sizeof(Vec2), &(TextObj->m_BgVerts[0]));
		_glVertexAttribPointer(0, 2, GL_FLOAT, GL_TRUE, 0, NULL);
		_glEnableVertexAttribArray(0);
		_glDisableVertexAttribArray(1);
		_glDisableVertexAttribArray(2);

		if( TextObj->m_BgColors.size()==TextObj->m_BgVerts.size() && _BgColor==0 )
		{
			_glBindBuffer(GL_ARRAY_BUFFER, m_TriColors);
			_glBufferSubData(GL_ARRAY_BUFFER, 0, numBgVerts*sizeof(color32), &(TextObj->m_BgColors[0]));
			_glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);
			_glEnableVertexAttribArray(1);

			_glUseProgram(m_TriProgram);
			_glUniform2f(m_TriLocationOffset, (float)_X, (float)_Y);
			_glUniform2f(m_TriLocationWndSize, (float)m_WndWidth, (float)m_WndHeight);
		}
		else
		{
			_glUseProgram(m_TriUniProgram);
			_glUniform4f(m_TriUniLocationColor, GLfloat((_BgColor>>16)&0xff)/256.0f, GLfloat((_BgColor>>8)&0xff)/256.0f, GLfloat(_BgColor&0xff)/256.0f, GLfloat((_BgColor>>24)&0xff)/256.0f);
			_glUniform2f(m_TriUniLocationOffset, (float)_X, (float)_Y);
			_glUniform2f(m_TriUniLocationWndSize, (float)m_WndWidth, (float)m_WndHeight);
		}

		_glDrawArrays(GL_TRIANGLES, 0, (GLsizei)TextObj->m_BgVerts.size());
	}

	// draw character triangles
	if( TextObj->m_TextVerts.size()>=4 )
	{
		_glBindTexture(GL_TEXTURE_2D, m_FontTexID);
		size_t numTextVerts = TextObj->m_TextVerts.size();
		if( numTextVerts > m_TriBufferSize )
			ResizeTriBuffers(numTextVerts + 2048);

		_glBindVertexArray(m_TriVArray);
		_glDisableVertexAttribArray(2);

		_glBindBuffer(GL_ARRAY_BUFFER, m_TriVertices);
		_glBufferSubData(GL_ARRAY_BUFFER, 0, numTextVerts*sizeof(Vec2), &(TextObj->m_TextVerts[0]));
		_glVertexAttribPointer(0, 2, GL_FLOAT, GL_TRUE, 0, NULL);
		_glEnableVertexAttribArray(0);

		_glBindBuffer(GL_ARRAY_BUFFER, m_TriUVs);
		_glBufferSubData(GL_ARRAY_BUFFER, 0, numTextVerts*sizeof(Vec2), &(TextObj->m_TextUVs[0]));
		_glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, NULL);
		_glEnableVertexAttribArray(1);

		if( TextObj->m_Colors.size()==TextObj->m_TextVerts.size() && _Color==0 )
		{
			_glBindBuffer(GL_ARRAY_BUFFER, m_TriColors);
			_glBufferSubData(GL_ARRAY_BUFFER, 0, numTextVerts*sizeof(color32), &(TextObj->m_Colors[0]));
			_glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);
			_glEnableVertexAttribArray(2);

			_glUseProgram(m_TriTexProgram);
			_glUniform2f(m_TriTexLocationOffset, (float)_X, (float)_Y);
			_glUniform2f(m_TriTexLocationWndSize, (float)m_WndWidth, (float)m_WndHeight);
			_glUniform1i(m_TriTexLocationTexture, 0);
		}
		else
		{
			_glUseProgram(m_TriTexUniProgram);
			_glUniform4f(m_TriTexUniLocationColor, GLfloat((_Color>>16)&0xff)/256.0f, GLfloat((_Color>>8)&0xff)/256.0f, GLfloat(_Color&0xff)/256.0f, GLfloat((_Color>>24)&0xff)/256.0f);
			_glUniform2f(m_TriTexUniLocationOffset, (float)_X, (float)_Y);
			_glUniform2f(m_TriTexUniLocationWndSize, (float)m_WndWidth, (float)m_WndHeight);
			_glUniform1i(m_TriTexUniLocationTexture, 0);
		}

		_glDrawArrays(GL_TRIANGLES, 0, (GLsizei)TextObj->m_TextVerts.size());
	}

	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::ChangeViewport(int _X0, int _Y0, int _Width, int _Height, int _OffsetX, int _OffsetY)
{
	// glViewport impacts the NDC; use glScissor instead
	m_OffsetX = _X0 + _OffsetX;
	m_OffsetY = _Y0 + _OffsetY;
	SetScissor(_X0, _Y0, _Width, _Height);
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::RestoreViewport()
{
	m_OffsetX = m_OffsetY = 0;
	SetScissor(0, 0, 0, 0);
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::SetScissor(int _X0, int _Y0, int _Width, int _Height)
{
	if( _Width>0 && _Height>0 )
	{
		_glScissor(_X0-1, m_WndHeight-_Y0-_Height, _Width-1, _Height);
		_glEnable(GL_SCISSOR_TEST);
	}
	else
		_glDisable(GL_SCISSOR_TEST);
	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------

void CTwGraphOpenGLCore::DrawTriangles(int _NumTriangles, int *_Vertices, color32 *_Colors, Cull _CullMode)
{
	assert(m_Drawing==true);

	const GLfloat dx = +0.0f;
	const GLfloat dy = +0.0f;

	// Backup states
	GLint prevCullFaceMode, prevFrontFace;
	_glGetIntegerv(GL_CULL_FACE_MODE, &prevCullFaceMode);
	_glGetIntegerv(GL_FRONT_FACE, &prevFrontFace);
	GLboolean prevCullEnable = _glIsEnabled(GL_CULL_FACE);
	_glCullFace(GL_BACK);
	_glEnable(GL_CULL_FACE);
	if( _CullMode==CULL_CW )
		_glFrontFace(GL_CCW);
	else if( _CullMode==CULL_CCW )
		_glFrontFace(GL_CW);
	else
		_glDisable(GL_CULL_FACE);

	_glUseProgram(m_TriProgram);
	_glBindVertexArray(m_TriVArray);
	_glUniform2f(m_TriLocationOffset, (float)m_OffsetX+dx, (float)m_OffsetY+dy);
	_glUniform2f(m_TriLocationWndSize, (float)m_WndWidth, (float)m_WndHeight);
	_glDisableVertexAttribArray(2);

	size_t numVerts = 3*_NumTriangles;
	if( numVerts > m_TriBufferSize )
		ResizeTriBuffers(numVerts + 2048);

	_glBindBuffer(GL_ARRAY_BUFFER, m_TriVertices);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, numVerts*2*sizeof(int), _Vertices);
	_glVertexAttribPointer(0, 2, GL_INT, GL_FALSE, 0, NULL);
	_glEnableVertexAttribArray(0);

	_glBindBuffer(GL_ARRAY_BUFFER, m_TriColors);
	_glBufferSubData(GL_ARRAY_BUFFER, 0, numVerts*sizeof(color32), _Colors);
	_glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);
	_glEnableVertexAttribArray(1);

	_glDrawArrays(GL_TRIANGLES, 0, (GLsizei)numVerts);

	// Reset states
	_glCullFace(prevCullFaceMode);
	_glFrontFace(prevFrontFace);
	if( prevCullEnable )
		_glEnable(GL_CULL_FACE);
	else
		_glDisable(GL_CULL_FACE);
	CHECK_GL_ERROR;
}

//	---------------------------------------------------------------------------
}
#endif // !defined ANT_TW_NO_CORE_GL
